﻿namespace wToggl
{
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices.WindowsRuntime;

    using Windows.UI.Notifications;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Input;
    using Windows.UI.Xaml.Media;

    public sealed partial class TagSelector : INotifyPropertyChanged
    {
        private IEnumerable<ItemHolder> itemsInternal = new List<ItemHolder>();

        private bool isMultiSelect = true;

        public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.RegisterAttached(
            "ItemTemplate",
            typeof(DataTemplate),
            typeof(TagSelector),
            new PropertyMetadata(default(DataTemplate), DataTemplateChangedCallback));

        public static readonly DependencyProperty ItemsProperty = DependencyProperty.RegisterAttached(
            "Items",
            typeof(IEnumerable),
            typeof(TagSelector),
            new PropertyMetadata(default(IEnumerable), ItemsChangedCallback));

        public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached(
            "SelectedItems",
            typeof(IEnumerable),
            typeof(TagSelector),
            new PropertyMetadata(default(IEnumerable), SelectedItemsChangedCallback));

        private static void SelectedItemsChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var obj = dependencyObject as TagSelector;

            if (obj == null)
            {
                return;
            }

            if (dependencyPropertyChangedEventArgs.NewValue == dependencyPropertyChangedEventArgs.OldValue)
            {
                return;
            }

            var newValue = dependencyPropertyChangedEventArgs.NewValue as IEnumerable;

            if (newValue == null)
            {
                foreach (var item in obj.itemsInternal)
                {
                    item.IsSelected = false;
                }

                obj.OnPropertyChanged("SelectedText");
                return;
            }

            var enumerable = newValue.Cast<object>().ToList();

            foreach (var item in obj.itemsInternal)
            {
                if (obj.IsMultiSelect)
                {
                    item.IsSelected = enumerable.Any(i => i.Equals(item.Data));
                    if (item.IsSelected)
                    {
                        obj.ListView.SelectedItems.Add(item);
                    }
                }
                else
                {
                    item.IsSelected = enumerable.Any(i => i.Equals(item.Data));
                }
            }
         
            obj.OnPropertyChanged("SelectedText");
        }

        public static void SetSelectedItems(DependencyObject element, IEnumerable value)
        {
            element.SetValue(SelectedItemsProperty, value);
        }

        public static IEnumerable GetSelectedItems(DependencyObject element)
        {
            return (IEnumerable)element.GetValue(SelectedItemsProperty);
        }

        public TagSelector()
        {
            this.InitializeComponent();
            this.DisplayBox.DataContext = this;
            this.ListView.DataContext = this;
        }

        private static void DataTemplateChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var obj = dependencyObject as TagSelector;
            var newValue = dependencyPropertyChangedEventArgs.NewValue as DataTemplate;

            if (obj == null)
            {
                return;
            }

            foreach (var item in obj.itemsInternal)
            {
                item.ItemTemplate = newValue;
            }
        }

        private static void ItemsChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var obj = dependencyObject as TagSelector;
            var newValue = dependencyPropertyChangedEventArgs.NewValue as IEnumerable;

            if (obj == null || newValue == null)
            {
                return;
            }

            obj.ItemsInternal =
                newValue.Cast<object>()
                    .Select(i => new ItemHolder { IsSelected = false, Data = i, ItemTemplate = GetItemTemplate(obj) })
                    .ToList();
        }

        public IEnumerable<ItemHolder> ItemsInternal
        {
            get
            {
                return this.itemsInternal;
            }
            set
            {
                this.itemsInternal = value;
                this.OnPropertyChanged();
            }
        }

        public bool IsMultiSelect
        {
            get
            {
                return this.isMultiSelect;
            }

            set
            {
                this.isMultiSelect = value;

                this.OnPropertyChanged("SelectedText");
            }
        }

        public string PlaceholderText
        {
            get
            {
                return this.DisplayBox.PlaceholderText;
            }

            set
            {
                this.DisplayBox.PlaceholderText = value;
            }
        }

        public string Header
        {
            get
            {
                return this.HeaderBlock.Text;
            }

            set
            {
                this.HeaderBlock.Text = value;
            }
        }

        public static void SetItemTemplate(DependencyObject element, DataTemplate value)
        {
            element.SetValue(ItemTemplateProperty, value);
        }

        public static DataTemplate GetItemTemplate(DependencyObject element)
        {
            return (DataTemplate)element.GetValue(ItemTemplateProperty);
        }

        public static void SetItems(DependencyObject element, IEnumerable value)
        {
            element.SetValue(ItemsProperty, value);
        }

        public static IEnumerable GetItems(DependencyObject element)
        {
            return (IEnumerable)element.GetValue(ItemsProperty);
        }

        public string SelectedText
        {
            get
            {
                return string.Join(", ", this.itemsInternal.Where(i => i.IsSelected).Select(i => i.Data));
            }
        }

        public DataTemplate ItemTemplate
        {
            get
            {
                return (DataTemplate)this.GetValue(ItemTemplateProperty);
            }

            set
            {
                this.SetValue(ItemTemplateProperty, value);
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
        {
            this.ListView.Visibility = Visibility.Visible;
            this.ScrollViewer.ClearValue(ScrollViewer.HeightProperty);
        }

        private void ToggleButton_OnUnchecked(object sender, RoutedEventArgs e)
        {
            this.ListView.Visibility = Visibility.Collapsed;
            this.ScrollViewer.SetValue(ScrollViewer.HeightProperty, 0);
            this.OnPropertyChanged("SelectedText");
        }

        private void ListView_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (!this.IsMultiSelect)
            {
                if (this.ListView.SelectedIndex == -1)
                {
                    foreach (var itemHolder in this.ItemsInternal)
                    {
                        itemHolder.IsSelected = false;
                    }
                    return;
                }

                ItemHolder holder = null;

                if (e.AddedItems.Any())
                {
                    holder = (ItemHolder)e.AddedItems[0];
                }
                else if (e.RemovedItems.Any())
                {
                    holder = (ItemHolder)e.RemovedItems[0];    
                }

                var originalValue = holder != null && holder.IsSelected;
                this.ListView.SelectedIndex = -1;

                if (holder != null)
                {
                    holder.IsSelected = !originalValue;

                    var asEnumerable = this.itemsInternal.Where(i => i.IsSelected).Select(i => i.Data).AsEnumerable();
                    this.SetValue(
                        SelectedItemsProperty,
                        asEnumerable);
                }             
            }
            else
            {
                foreach (ItemHolder selected in e.AddedItems)
                {
                    selected.IsSelected = true;
                }

                foreach (ItemHolder selected in e.RemovedItems)
                {
                    selected.IsSelected = false;
                }

                var asEnumerable = this.itemsInternal.Where(i => i.IsSelected).Select(i => i.Data).AsEnumerable();
                this.SetValue(
                    SelectedItemsProperty,
                    asEnumerable);
            } 
        }

        public class ItemHolder : INotifyPropertyChanged
        {
            private bool isSelected;

            private object data;

            private DataTemplate itemTemplate;

            public bool IsSelected
            {
                get
                {
                    return this.isSelected;
                }
                set
                {
                    this.isSelected = value;
                    this.OnPropertyChanged();
                }
            }

            public object Data
            {
                get
                {
                    return this.data;
                }
                set
                {
                    this.data = value;
                    this.OnPropertyChanged();
                }
            }

            public DataTemplate ItemTemplate
            {
                get
                {
                    return this.itemTemplate;
                }
                set
                {
                    this.itemTemplate = value;
                    this.OnPropertyChanged();
                }
            }

            public event PropertyChangedEventHandler PropertyChanged;

            protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                var handler = this.PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }

        private void OnLostFocus(object sender, RoutedEventArgs e)
        {
            var focused = FocusManager.GetFocusedElement();

            if (focused != null)
            {
                var root = (DependencyObject)this.LayoutRoot;
                var queue = new Queue<DependencyObject>();
                queue.Enqueue(root);

                while (queue.Count > 0)
                {
                    var item = queue.Dequeue();

                    var childrenCount = VisualTreeHelper.GetChildrenCount(item);

                    for (var i = 0; i < childrenCount; i++)
                    {
                        var child = VisualTreeHelper.GetChild(item, i);

                        if (child == focused)
                        {
                            return;
                        }
                        else
                        {
                            queue.Enqueue(child);
                        }
                    }
                }
            }

            this.ToggleButton.IsChecked = false;
        }
    }
}
